/***************************************************************************
 *   Copyright (C) 2007 by Ivan Vasić              *
 *   ivasic@gmail.com                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "trackerdb.h"
#include "mysqlfactory.h"
#include "bencoding.h"
#include "sha1hash.h"
#include "sha1hashgen.h"
#include "exceptions.h"
#include "sqlobjectsmodel.h"
#include "modeltorrent.h"

#include <QtSql>
#include <QtCore>
#include <QtGui>

TrackerDB::TrackerDB(DBMS dbType, const QString& dbname, const QString& host, int port, const QString& username, const QString& password, const QString& configFile, QObject *parent)
		: QObject(parent), m_dbms(dbType), m_dbname(dbname), m_host(host), m_username(username), m_password(password), m_configFile(configFile), m_port(port)
{
	//Otvori konekciju
	createConnection();

	///@TODO: Postavi funkciju za setovanje drugih factory-ja. Trenutno hardcoded MYSQL jer se samo to koristi.
	m_dbFactory = new MySQLFactory();
}


TrackerDB::~TrackerDB()
{
	if (m_dbFactory)
		delete m_dbFactory;
}

bool TrackerDB::createConnection()
{
	QSqlDatabase db = QSqlDatabase::addDatabase(DB_DRIVER);

	db.setDatabaseName(m_dbname);
	db.setHostName(m_host);
	db.setUserName(m_username);
	db.setPassword(m_password);
	db.setPort(m_port);

	if (!db.isValid())
	{
		return false;
	}

	if (!db.open())
	{
		throw DBException(tr("Database could not be opened! Application will now exit. Manually edit <b>%1</b> file and set database connection parameters.").arg(m_configFile), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
		/*   QMessageBox::critical(0, qApp->tr("Cannot open database"),
		       qApp->tr("Unable to establish a database connection.\n"
		                "Click Cancel to exit."), QMessageBox::Cancel);*/
	}

	//Create dummy table to know if database exists and is valid.
	/*query.exec("CREATE TABLE dummy (id INTEGER PRIMARY KEY)");
	query.exec("INSERT INTO dummy VALUES (NULL)");

	//Create tables
	query.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, number TEXT");
	*/

	QFile dbSql(QApplication::applicationDirPath() + "/Database.sql");
	if (dbSql.exists())
	{
		dbSql.open(QFile::ReadOnly);
		QSqlQuery query;
		query.prepare(dbSql.readAll());

		query.exec();
	}

	dbSql.close();

	return true;
}

AnnounceHandler* TrackerDB::createAnnounceHandler() const
{
	checkDBFactory();

	return m_dbFactory->createAnnounceHandler();
}

bool TrackerDB::isTorrentRegistered(const SHA1Hash & infohash) const
{
	checkDBFactory();

	return m_dbFactory->isTorrentRegistered(infohash);
}

void TrackerDB::registerTorrent(const QByteArray & metainfo, const QString & name, const QString & description)
{
	checkDBFactory();

	//Prvo dekodiraj torrent metainfo
	BDecoder dec(metainfo);
	BNode* root = dec.decode();

	//Ako nije uspeo da dekodira ili rezultat nije BDict (sto je nepravilno) throw exception
	if (!root || root->getType() != BNode::DICT)
		throw new DecodingException(tr("Could not decode metainfo data."), Exception::Normal, __FILE__, __FUNCTION__, __LINE__);

	//Nadji vrednost info cvora
	BDictNode* r = dynamic_cast<BDictNode*>(root);
	const BNode* info = r->value("info");

	if (!info)
	{
		//brisi root
		delete r;
		throw new DecodingException(tr("Torrent does not have required 'info' node."), Exception::Normal, __FILE__, __FUNCTION__, __LINE__);
	}

	//pa za info cvor generisi SHA1Hash
	SHA1HashGen hg;
	SHA1Hash infohash = hg.generate((quint8*)metainfo.data() + info->offset(), info->length());

	if (isTorrentRegistered(infohash))
	{
		//brisi root
		delete r;
		throw new TorrentExistsException(tr("Torrent already exists: %1 ").arg(infohash.toString()), Exception::Normal, __FILE__, __FUNCTION__, __LINE__);
	}


	//Sad proveravam da li je Announce URL korektnog formata tj. da sadrzi passkey user-a koji ga je kreirao!
	const BValueNode* announce = dynamic_cast<const BValueNode*>(r->value("announce"));

	if (!announce)
	{
		//brisi root
		delete r;

		throw new DecodingException(tr("Torrent does not have required 'announce' node."), Exception::Normal, __FILE__, __FUNCTION__, __LINE__);
	}


	QString url = announce->value().toString();

	QRegExp reg("/\\S{20,20}/announce$");

	int pstart = reg.indexIn(url);
	if (pstart == -1)
	{
		//brisi root
		delete r;

		throw new InvalidAnnounceURLException(tr("Announce URL is not in expected format"), Exception::Normal, __FILE__, __FUNCTION__, __LINE__);
	}

	QString passkey = url.mid(pstart + 1, 20);
	QString* username = new QString();

	if (!m_dbFactory->passkeyExists(passkey, username))
	{
		//brisi root
		delete r;

		throw new UnknownUserException(tr("Passkey found in torrent does not exist"), Exception::Normal, __FILE__, __FUNCTION__, __LINE__);
	}

	//izracunaj total size torrent-a
	quint64 torrent_size = calculateTorrentSize(dynamic_cast<const BDictNode*>(info));


	//brisi root
	delete r;

	//Na kraju registrujem torrent
	qDebug() << tr("Registering torrent with infohash: ") << infohash.toString();

	m_dbFactory->registerTorrent(infohash, metainfo, name, description, torrent_size, *username);
}


void TrackerDB::onTimeout()
{
	checkDBFactory();

	m_dbFactory->timeout();
}

void TrackerDB::registerUser(const QString & username, const QString & password, const QString & email, const QString& name)
{
	checkDBFactory();

	m_dbFactory->registerUser(username, password, email, name);
}

bool TrackerDB::userExists(const QString& username, const QString& email, bool* userExists, bool* emailExists)
{
	checkDBFactory();

	return m_dbFactory->userExists(username, email, userExists, emailExists);
}

SqlObjectsModel* TrackerDB::createTorrentsSqlModel()
{
	checkDBFactory();

	return m_dbFactory->createTorrentsSqlModel();
}

SqlObjectsModel * TrackerDB::createUsersSqlModel()
{
	checkDBFactory();

	return m_dbFactory->createUsersSqlModel();
}


ModelTorrent * TrackerDB::createModelTorrent(const QString id)
{
	checkDBFactory();

	return m_dbFactory->createModelTorrent(id);
}

ModelUser * TrackerDB::createModelUser(const QString id)
{
	checkDBFactory();

	return m_dbFactory->createModelUser(id);
}

QStringList TrackerDB::getAllUsernames() const
{
	checkDBFactory();

	return m_dbFactory->getAllUsernames();
}

bool TrackerDB::passkeyExists(const QString& passkey, QString* username)
{
	checkDBFactory();

	return m_dbFactory->passkeyExists(passkey, username);
}

void TrackerDB::checkDBFactory() const
{
	if (!m_dbFactory)
	{
		throw;
	}
}

quint64 TrackerDB::calculateTorrentSize(const BDictNode* info) const
{
	const BNode* filesNode = info->value("files");

	if (!filesNode) // single file torrent!
	{
		const BValueNode* length = dynamic_cast<const BValueNode*>(info->value("length"));
		return length->value().toULongLong();
	}

	const BListNode* files = dynamic_cast<const BListNode*>(filesNode);

	quint64 total_size = 0l;

	//multifile torrent!
	for (int i = 0; i < files->children().count(); ++i)
	{
		const BDictNode* file = dynamic_cast<const BDictNode*>(files->children().value(i));
		const BValueNode* length = dynamic_cast<const BValueNode*>(file->value("length"));

		total_size += length->value().toULongLong();
	}

	return total_size;
}

void TrackerDB::resetPasskey(const QString & username, const QString & password)
{
	checkDBFactory();

	return m_dbFactory->resetPasskey(username, password);
}

void TrackerDB::changeUserInfo(const QString& username, const QString & password, const QString & email)
{
	checkDBFactory();

	return m_dbFactory->changeUserInfo(username, password, email);
}

void TrackerDB::updateTorrentDescription(const QString & infohash, const QString & newDescription)
{
	checkDBFactory();

	return m_dbFactory->updateTorrentDescription(infohash, newDescription);
}

QAbstractItemModel* TrackerDB::getTorrentListForUser(const QString & username, bool complete) const
{
	checkDBFactory();

	return m_dbFactory->getTorrentListForUser(username, complete);
}


int TrackerDB::usersOnline() const
{
	checkDBFactory();

	return m_dbFactory->usersOnline();
}

void TrackerDB::countPeers(const QString & infohash, int * seeders, int * leechers) const
{
	checkDBFactory();

	m_dbFactory->countPeers(infohash, seeders, leechers);
}

void TrackerDB::countTotalPeers(int * seeders, int * leechers) const
{
	checkDBFactory();

	m_dbFactory->countTotalPeers(seeders, leechers);
}
